#include <stdafx.h>
#include "ScriptEditorWidget.h"
#include "ScriptEditorTabWidget.h"
#include "ScriptLogOutput.h"
#include <LitEditorFileDialog.h>
		
namespace Lit
{
	namespace Editor
	{ 
		ScriptHighlighter::ScriptHighlighter(QTextDocument *parent)
			: QSyntaxHighlighter(parent)
		{

			keywordFormat = ScriptSyntaxPolice( "Courrier New", QFont::Bold, Qt::darkBlue);
			QStringList keywordPatterns;
			keywordPatterns << "\\bchar\\b" << "\\bclass\\b" << "\\bconst\\b"
							<< "\\bdouble\\b" << "\\benum\\b" << "\\bexplicit\\b"
							<< "\\bfriend\\b" << "\\binline\\b" << "\\bint\\b"
							<< "\\blong\\b" << "\\bnamespace\\b" << "\\boperator\\b"
							<< "\\bprivate\\b" << "\\bprotected\\b" << "\\bpublic\\b"
							<< "\\bshort\\b" << "\\bsignals\\b" << "\\bsigned\\b"
							<< "\\bslots\\b" << "\\bstatic\\b" << "\\bstruct\\b"
							<< "\\btemplate\\b" << "\\btypedef\\b" << "\\btypename\\b"
							<< "\\bunion\\b" << "\\bunsigned\\b" << "\\bvirtual\\b"
							<< "\\bvoid\\b" << "\\bvolatile\\b";
			foreach (const QString &pattern, keywordPatterns) {
				highlightingRules.append(ScriptSyntaxRule(pattern.toAscii().data(),keywordFormat));
			}
			
			classFormat = ScriptSyntaxPolice( "Courrier New", QFont::Bold, Qt::darkMagenta );
			highlightingRules.append(ScriptSyntaxRule("\\bQ[A-Za-z]+\\b",classFormat));
			
			quotationFormat = ScriptSyntaxPolice( "Courrier New", QFont::Normal, Qt::red);
			highlightingRules.append(ScriptSyntaxRule("\".*\"",quotationFormat));
			
			functionFormat = ScriptSyntaxPolice( "Courrier New", QFont::Normal, Qt::blue, false, true);
			highlightingRules.append(ScriptSyntaxRule("\\b[A-Za-z0-9_]+(?=\\()",functionFormat));

			singleLineCommentFormat = ScriptSyntaxPolice( "Courrier New", QFont::Normal, Qt::darkGreen );
			highlightingRules.append(ScriptSyntaxRule("//[^\n]*",singleLineCommentFormat));

			multiLineCommentFormat = ScriptSyntaxPolice( "Courrier New", QFont::Normal, Qt::darkGreen );

			commentStartExpression = QRegExp("/\\*");
			commentEndExpression = QRegExp("\\*/");
		}
		void ScriptHighlighter::highlightBlock(const QString &text)
		{
			foreach (const ScriptSyntaxRule &rule, highlightingRules) {
				QRegExp expression(rule.pattern);
				int index = expression.indexIn(text);
				while (index >= 0) {
					int length = expression.matchedLength();
					setFormat(index, length, rule.police.format);
					index = expression.indexIn(text, index + length);
				}
			}
			setCurrentBlockState(0);
			int startIndex = 0;
			if (previousBlockState() != 1)
				startIndex = commentStartExpression.indexIn(text);
			while (startIndex >= 0) {
				 int endIndex = commentEndExpression.indexIn(text, startIndex);
				 int commentLength;
				 if (endIndex == -1) {
					 setCurrentBlockState(1);
					 commentLength = text.length() - startIndex;
				 } else {
					 commentLength = endIndex - startIndex
									 + commentEndExpression.matchedLength();
				 }
				 setFormat(startIndex, commentLength, multiLineCommentFormat.format);
				 startIndex = commentStartExpression.indexIn(text, startIndex + commentLength);
			 }
		}
		ScriptEditorMarginWidget::ScriptEditorMarginWidget(ScriptEditorWidget *edt) 
			: QWidget(edt) 
		{
				mEditor = edt;
		}
		ScriptEditorMarginWidget::~ScriptEditorMarginWidget(){ }

		QSize ScriptEditorMarginWidget::sizeHint() const {
			return QSize(mEditor->lineNumberAreaWidth(), 0);
		}

		void ScriptEditorMarginWidget::paintEvent(QPaintEvent *event) {
			mEditor->lineNumberAreaPaintEvent(event);
		}

		ScriptEditorCompleter::ScriptEditorCompleter(ScriptEditorWidget * parent) : mEditor(parent), QListWidget( parent )
		{
			installEventFilter( this );

			hide();

			addKeywords(QStringList() << "char" << "class" << "const"
							<< "double" << "enum" << "string" << "explicit"
							<< "friend" << "inline" << "int"
							<< "long" << "namespace" << "operator"
							<< "private" << "protected" << "public"
							<< "short" << "signals" << "signed"
							<< "slots" << "static" << "struct"
							<< "template" << "typedef" << "typename"
							<< "union" << "unsigned" << "virtual"
							<< "void" << "volatile");

			connect( (QListWidget*)this, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT( complete(QListWidgetItem*) ) );
		}
		ScriptEditorCompleter::~ScriptEditorCompleter( ){
		}
		
		void ScriptEditorCompleter::complete( QListWidgetItem* itm ){
			if( itm )
			{
				QTextCursor tc(mEditor->textCursor());
				int pos = tc.position();
				tc.movePosition( QTextCursor::StartOfWord );
				tc.setPosition( pos, QTextCursor::KeepAnchor );
				QString sel=tc.selection().toPlainText();
				tc.removeSelectedText();
				tc.insertText(itm->text());
				
				tc.movePosition( QTextCursor::EndOfWord );

				mEditor->setTextCursor( tc );
				mEditor->setFocus();
				hide();
			}
		}
		bool ScriptEditorCompleter::eventFilter( QObject * obj, QEvent * evt )
		{
			if( obj == (QObject*)this )
			{
				if( evt->type() == QEvent::FocusOut )
				{
					hide();
					return false;
				}
			}
			return QListWidget::eventFilter( obj, evt );
		}
		void ScriptEditorCompleter::keyPressEvent( QKeyEvent * evt )
		{
			if( evt->key() == Qt::Key_Escape )
			{
				hide();
				mEditor->setFocus();
			}
			else if( evt->key() == Qt::Key_Up )
			{
				int r = currentRow();
				if( r > 0 )
					r -= 1;
				setCurrentRow( r );
			}
			else if( evt->key() == Qt::Key_Down )
			{
				int r = currentRow();
				if( r < count()-1 )
					r += 1;
				setCurrentRow( r );
			}
			else if( evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return )
			{
				complete( currentItem() );
			}
		}
		void ScriptEditorCompleter::showCompletion( const std::string &word ) 
		{
			hide();

			for( auto i=mWords.begin(); i!=mWords.end(); i++ )
			{
				std::string w = word;
				Ogre::StringUtil::trim( w );
				if( w.empty() || i->startsWith( w.c_str() ) )
				{
					addItem( *i );
				}
			}
			setCurrentRow( 0 );
			if( count() != 0 )
			{
				QRect curRect = mEditor->cursorRect();
				QPoint localPos = QPoint(curRect.x(), curRect.y());
				show( localPos );
			}
			else hide();
		}
		
		void ScriptEditorCompleter::hide(){
			clear();
			QListWidget::hide();
			setGeometry( 0,0, 0, 0 );
		}
		void ScriptEditorCompleter::show( QPoint pos ){
			setGeometry( pos.x(), pos.y(), 200, 180 );
			QListWidget::show();
			activateWindow();
			update();

			int sz = 0;
			for( int i=0; i<count(); i++ )
				sz += sizeHintForRow( i );
			setGeometry( pos.x(), pos.y(), 200, std::min(sz,180) );
			setFocus();
		}

		bool ScriptEditorCompleter::hasKeyword( const QString &word ) const
		{
			for( auto i=mWords.begin(); i!=mWords.end(); i++ ) if( *i == word ) return true;
			return false;
		}
		void ScriptEditorCompleter::addKeywords( const QStringList& words )
		{
			for( auto i=words.begin(); i!=words.end(); i++ )
				if( !hasKeyword( *i ) )
					mWords.push_back( *i );
		}

		ScriptEditorWidget::ScriptEditorWidget( InstancePtr inst, ScriptTabWidget * parent )
			: QPlainTextEdit(parent)
			, mId( (size_t)parent->count() )
			, mInstance(inst)
			, mTabWidget(parent)
		{
			mMargin = new ScriptEditorMarginWidget( this );
			mDefaultFont = QFont( "Courrier New", 10 );
			mHighlighter = new ScriptHighlighter( document() );
			mCompleter = new ScriptEditorCompleter(this);			

			updateLineNumberAreaWidth(0);
			highlightCurrentLine();

			setFont( mDefaultFont );
			setWordWrapMode( QTextOption::NoWrap );
			setTabStopWidth( 20 );
			
			connect( (QPlainTextEdit*)this, SIGNAL(modificationChanged(bool)), this, SLOT(modifiedStateChanged(bool)) );
			connect( this, SIGNAL(modifiedStateChanged(ScriptEditorWidget *,bool)), parent, SLOT(modifiedStateChanged(ScriptEditorWidget *,bool)) );
		}
		ScriptEditorWidget::~ScriptEditorWidget(){
			if( mMargin ) delete mMargin, mMargin = NULL;
			if( mHighlighter ) delete mHighlighter, mHighlighter = NULL;
		}
			
		ScriptEditorCompleter *					ScriptEditorWidget::getCompleter(){ return mCompleter; }
		const ScriptEditorCompleter *			ScriptEditorWidget::getCompleter() const{ return mCompleter; }
		size_t ScriptEditorWidget::getIndentation() const
		{
			QTextCursor curs(textCursor());
			bool tok_open = false;
			// Auto-indentation
			QString data = toPlainText();
			int cursorPosition = curs.position();
			int i;
			for (i=cursorPosition-2; i>=0; i--){
				if (data.mid(i,1) == "\n") {
				break;
				}
				else if( data.mid(i,1) == "{" )
				{
					tok_open = true;
				}
			}
			size_t ret=0;
			while (data.mid(i+1,1) == "\t")
			{
				i++;
				ret ++;
			}
			if( tok_open ) ret++;
			return ret;
		}
		void ScriptEditorWidget::indentSelection(){
			QTextCursor curs(textCursor());
			QString txt(curs.selection().toPlainText());
			QStringList lst = txt.split("\n");
			for( auto i=lst.begin(); i!=lst.end(); i++ )
			{
				(*i) = "\t"+(*i);
			}
			QString after(lst.join("\n") );
			curs.removeSelectedText();
			int stPos = curs.position();
			curs.insertText(after);
			int ePos = curs.position();
			curs.setPosition(stPos, QTextCursor::MoveAnchor );
			curs.setPosition(ePos, QTextCursor::KeepAnchor );
			setTextCursor( curs );
		}
		void ScriptEditorWidget::unindentSelection(){
			QTextCursor curs(textCursor());
			QString txt(curs.selection().toPlainText());
			QStringList lst = txt.split("\n");
			for( auto i=lst.begin(); i!=lst.end(); i++ )
			{
				if( (*i).at(0) == '\t' )
					(*i).remove( 0, 1 );
			}
			QString after(lst.join("\n") );
			curs.removeSelectedText();
			int stPos = curs.position();
			curs.insertText(after);
			int ePos = curs.position();
			curs.setPosition(stPos, QTextCursor::MoveAnchor );
			curs.setPosition(ePos, QTextCursor::KeepAnchor );
			setTextCursor( curs );
		}
		
		void ScriptEditorWidget::paintEvent( QPaintEvent * evt )
		{
			updateLineNumberAreaWidth(0);
			lineNumberAreaPaintEvent(evt);
			highlightCurrentLine();
			QPlainTextEdit::paintEvent(evt);
		}
		void ScriptEditorWidget::showCompletion( const std::string &word )
		{
			mCompleter->showCompletion( word );
		}
		void ScriptEditorWidget::keyPressEvent( QKeyEvent * evt )
		{
			bool skip = false;
			mCompleter->keyPressEvent(evt);

			if( evt->key() == Qt::Key_BraceRight || 
				evt->key() == Qt::Key_Backtab || 
				evt->key() == Qt::Key_Tab ||
				( evt->key() == Qt::Key_Space && (evt->modifiers() & Qt::ControlModifier) ) 
			)
				skip = true;
			
			if( !skip ) 
				QPlainTextEdit::keyPressEvent(evt);

			if( evt->key() == Qt::Key_Space && (evt->modifiers() & Qt::ControlModifier) )
			{
				QTextCursor curs(textCursor());
				int pos=curs.position();
				curs.movePosition( QTextCursor::StartOfWord );
				curs.setPosition( pos, QTextCursor::KeepAnchor );
				QString sel = curs.selection().toPlainText();
				showCompletion( sel.toAscii().data() );
			}
			// Auto Indent
			else if( evt->key() == Qt::Key_Return || 
				evt->key() == Qt::Key_Enter )
			{
				QTextCursor curs(textCursor());
				size_t indent = getIndentation();
				for( int i=0;i<indent; i++ )
					curs.insertText("\t");
			}
			// Unindent multi select
			else if( evt->key() == Qt::Key_Backtab )
			{
				QTextCursor curs(textCursor());
				if (!curs.hasSelection()) {
					QPlainTextEdit::keyPressEvent( evt );
				}
				else {
					unindentSelection();
				}
			}
			// indent multi select
			else if( evt->key() == Qt::Key_Tab )
			{
				QTextCursor curs(textCursor());
				if (!curs.hasSelection()) {
					QPlainTextEdit::keyPressEvent( evt );
				}
				else {
					indentSelection();
				}
			}
			// unindent closing curly brace
			else if( evt->key() == Qt::Key_BraceRight )
			{
				QTextCursor curs(textCursor());
				size_t indent = getIndentation();
				if( indent != 0 ){
					indent -= 1;
					curs.movePosition( QTextCursor::StartOfLine );
					curs.movePosition( QTextCursor::EndOfWord, QTextCursor::KeepAnchor );
					if( curs.selectedText().contains( "{" ) )
					{
					}
					else
					{
						curs.removeSelectedText();
						for( int i=0;i<indent; i++ )
							curs.insertText("\t");
					}
					QPlainTextEdit::keyPressEvent(evt);
				}
			}

			update();
		}
		int ScriptEditorWidget::lineNumberAreaWidth()
		{
			int digits = 1;
			int max = qMax(1, blockCount());
			while (max >= 10) {
				max /= 10;
				++digits;
			}

			int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;

			return space;
		}
		void ScriptEditorWidget::updateLineNumberAreaWidth(int /* newBlockCount */)
		{
			setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
		}
		void ScriptEditorWidget::updateLineNumberArea(const QRect &rect, int dy)
		{
			if (dy)
				mMargin->scroll(0, dy);
			else
				mMargin->update(0, rect.y(), mMargin->width(), rect.height());

			if (rect.contains(viewport()->rect()))
				updateLineNumberAreaWidth(0);
		}
		void ScriptEditorWidget::resizeEvent(QResizeEvent *e)
		{
			QPlainTextEdit::resizeEvent(e);

			QRect cr = contentsRect();
			mMargin->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
		}
		void ScriptEditorWidget::highlightCurrentLine()
		{
			QList<QTextEdit::ExtraSelection> extraSelections;

			if (!isReadOnly()) {
				QTextEdit::ExtraSelection selection;

				QColor lineColor = QColor(Qt::yellow).lighter(160);

				selection.format.setBackground(lineColor);
				selection.format.setProperty(QTextFormat::FullWidthSelection, true);
				selection.cursor = textCursor();
				selection.cursor.clearSelection();
				extraSelections.append(selection);
			}

			setExtraSelections(extraSelections);
		}
		void ScriptEditorWidget::lineNumberAreaPaintEvent(QPaintEvent *event)
		{
			QPainter painter(mMargin);
			painter.fillRect(event->rect(), Qt::lightGray);

			QTextBlock block = firstVisibleBlock(); 
			int blockNumber = block.blockNumber();
			int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
			int bottom = top + (int) blockBoundingRect(block).height();

			while (block.isValid() && top <= event->rect().bottom()) {
				if (block.isVisible() && bottom >= event->rect().top()) {
					QString number = QString::number(blockNumber + 1);
					painter.setPen(Qt::black);
					painter.drawText(0, top, mMargin->width(), fontMetrics().height(),
									Qt::AlignRight, number);
				}

				block = block.next();
				top = bottom;
				bottom = top + (int) blockBoundingRect(block).height();
				++blockNumber;
			}
		}

		bool ScriptEditorWidget::isModified() const{ return document()->isModified(); }
		void ScriptEditorWidget::setModified( bool state ){ document()->setModified( state ); }
			
		std::string ScriptEditorWidget::getName( bool ext ) const
		{
			return getPath().fileName( ext );
		}
		void ScriptEditorWidget::modifiedStateChanged(bool state)
		{
			emit modifiedStateChanged( this, state );
		}
		void ScriptEditorWidget::saveScript( )
		{
			if( mPath.str().empty() )
			{
				mPath = FileDialog::getSaveFileName( NULL, "Save Script", ScriptUserPath(), "All AngelScript Files (*.as);;" );
			}

			FileStream strm( mPath, Stream::TextWrite );
			strm.write( getText() );
			strm.close();

			setModified( false );
		}
		void ScriptEditorWidget::saveScriptAs( )
		{
			FilePath ret = FileDialog::getSaveFileName( NULL, "Save Script", ScriptUserPath(), "All AngelScript Files (*.as);;" );
			if( !ret.exists() )
			{
				return;
			}
			mPath = ret;

			FileStream strm( mPath, Stream::TextWrite );
			strm.write( getText() );
			strm.close();

			setModified( false );
		}
		void ScriptEditorWidget::loadScript( )
		{
			if( mPath.str().empty() )
			{
				mPath = FileDialog::getOpenFileName( NULL, "Save Script", ScriptUserPath(), "All AngelScript Files (*.as);;" );
			}

			FileStream strm( mPath, Stream::TextRead );
			if( !strm.valid() )
			{
				QMessageBox::information( this, "Cannot load script", "Cannot open stream '"+QString(mPath.str().c_str())+"'" );
				return;
			}
			setText(strm.readAll());
			strm.close();

			setModified(false);
		}
		
		FilePath ScriptEditorWidget::getPath() const{ return mPath; }
		void ScriptEditorWidget::setPath( const FilePath& path ){ mPath = path; }

		void ScriptEditorWidget::setText( const std::string &txt ){ 
			setPlainText( QString(txt.c_str()) );
			updateLineNumberAreaWidth(0); 
		}
		std::string ScriptEditorWidget::getText() const{ return std::string(toPlainText().toAscii().data()); }

		size_t ScriptEditorWidget::getId() const{ return mId; }
	};
};